package com.tj.bo.trade.server.service;

import com.alibaba.fastjson.JSONObject;
import com.fqgj.common.utils.CollectionUtils;
import com.google.common.collect.Lists;
import com.tj.bo.account.client.AccountBalanceChangeService;
import com.tj.bo.account.client.DepositService;
import com.tj.bo.account.client.enums.IncomeEnum;
import com.tj.bo.account.client.vo.AccountIncomeVO;
import com.tj.bo.account.client.vo.UserDepositVO;
import com.tj.bo.common.conf.config.RedisConfigUtil;
import com.tj.bo.common.conf.util.CalculatorUtils;
import com.tj.bo.common.conf.util.DecimalUtils;
import com.tj.bo.product.client.vo.Product;
import com.tj.bo.trade.client.enums.OrderStatusEnum;
import com.tj.bo.trade.client.enums.ProfitStatusEnum;
import com.tj.bo.trade.client.enums.TradeTypeEnum;
import com.tj.bo.trade.server.dao.BinaryTradeDao;
import com.tj.bo.trade.server.dao.BinaryTradeFlowDao;
import com.tj.bo.trade.server.entity.BinaryTradeEntity;
import com.tj.bo.trade.server.entity.BinaryTradeFlowEntity;
import com.tj.bo.trade.server.ro.OddsVO;
import com.tj.bo.user.client.UserProductService;
import com.tj.bo.user.client.enums.TriggerTypeEnum;
import com.tj.bo.user.client.vo.input.UserProductOdds;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * @program: binary-option
 * @description
 * @author: binary
 * @create: 2020-06-16 16:49
 **/
@Service
@Transactional(rollbackFor = Exception.class)
public class TradeHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(TradeHandler.class);

    @Autowired
    private BinaryTradeDao binaryTradeDao;
    @Autowired
    private BinaryTradeFlowDao binaryTradeFlowDao;
    @Autowired
    private AccountBalanceChangeService accountBalanceChangeService;
    @Autowired
    private UserProductService userProductService;
    @Autowired
    private DepositService depositService;
    @Autowired
    private RedisConfigUtil redisDbConfigUtil;

    public boolean settlementOrders(BinaryTradeEntity entity, Product product, BigDecimal profitAmount, BigDecimal closingPrice) {
        if (entity.getOrderStatus() == OrderStatusEnum.CLOSING.getCode() || entity.getOrderStatus() == OrderStatusEnum.TIMEOUT_CLOSING.getCode()) {
            LOGGER.info("============ 交易订单任务已收盘，重复执行，tradeNo:{}, profitAmount:{} ==========", entity.getTradeNo(), profitAmount);
            return false;
        }
        entity.setClosingTime(new Date());
        entity.setClosingPrice(closingPrice);
        entity.setOrderStatus(OrderStatusEnum.CLOSING.getCode());
        entity.setProfitAmount(profitAmount);
        entity.setProfitStatus(profitAmount.compareTo(BigDecimal.ZERO) > 0 ? ProfitStatusEnum.PROFIT.getCode() : ProfitStatusEnum.UN_PROFIT.getCode());
        binaryTradeDao.updateByPrimaryKey(entity);
        LOGGER.info("============ 交易订单任务已收盘成功，tradeNo:{}, profitAmount:{} ==========", entity.getTradeNo(), profitAmount);

        //入账
        this.cashRecorded(entity.getUserCode(), entity.getTradeNo(), profitAmount);
        LOGGER.info("============ 交易收盘入账成功，tradeNo:{}, profitAmount:{} ==========", entity.getTradeNo(), profitAmount);

        BinaryTradeFlowEntity binaryTradeFlowEntity = new BinaryTradeFlowEntity()
                .setTradeNo(entity.getTradeNo())
                .setCreateDate(new Date())
                .setOrderStatus(OrderStatusEnum.CLOSING.getCode());
        binaryTradeFlowDao.insert(binaryTradeFlowEntity);
        return true;
    }

    /**
     * 入账
     */
    private void cashRecorded(String userCode, String tradeNo, BigDecimal profitAmount) {
        if (profitAmount.compareTo(BigDecimal.ZERO) > 0) {
            AccountIncomeVO accountIncome = new AccountIncomeVO();
            accountIncome.setUserCode(userCode);
            accountIncome.setOrderNo(tradeNo);
            accountIncome.setAmount(profitAmount);
            accountIncome.setGrants(BigDecimal.ZERO);
            accountIncome.setIncomeEnum(IncomeEnum.TRADE_PROFIT);
            accountBalanceChangeService.income(accountIncome);
        }
    }

    private OddsVO oddsCalculation(String userCode, Product product, BigDecimal productProfit) {

        String productCode = product.getProductCode();
        OddsVO odds = new OddsVO();
        TradeTypeEnum tradeTypeEnum = TradeTypeEnum.REAL_TRADE;
        if (redisDbConfigUtil.calculateOddsByVirtualAccount()) {
            tradeTypeEnum = TradeTypeEnum.VIRTUAL_TRADE;
        }

        List<BinaryTradeEntity> binaryTradeEntities = binaryTradeDao.selectBinaryTradeListByUserCodeAndProductCode(userCode, tradeTypeEnum.getCode(), OrderStatusEnum.CLOSING.getCode(), productCode);
        BigDecimal productWinRate = BigDecimal.ZERO;
        BigDecimal todayProductWinRate = BigDecimal.ZERO;
        BigDecimal todayProductProfitRate = BigDecimal.ZERO;
        BigDecimal historyProductWinRate = BigDecimal.ZERO;
        BigDecimal totalProfit = BigDecimal.ZERO;
        BigDecimal todayTotalProfit = BigDecimal.ZERO;
        BigDecimal todayTotalAmount = BigDecimal.ZERO;
        BigDecimal historyTotalProfit = BigDecimal.ZERO;
        String productOdds = userProductService.getSettlementProfit(productCode, userCode, productProfit, false).toString();
        Integer transactionsNumbers = 0;
        Integer winNumbers = 0;
        Integer todayTransactionsNumbers = 0;
        Integer todayWinNumbers = 0;
        Integer historyTransactionsNumbers = 0;
        Integer historyWinNumbers = 0;
        boolean isNewUser = true;
        List<Integer> amountList = Lists.newArrayList();
        List<Integer> historyAmountList = Lists.newArrayList();
        List<Integer> todayAmountList = Lists.newArrayList();
        if (!CollectionUtils.isEmpty(binaryTradeEntities)) {
            for (BinaryTradeEntity entity : binaryTradeEntities) {
                totalProfit = totalProfit.add(entity.getProfitAmount().subtract(entity.getAmount()));
                transactionsNumbers = transactionsNumbers + 1;
                amountList.add(entity.getAmount().intValue());
                if (entity.getProfitStatus() == ProfitStatusEnum.PROFIT.getCode()) {
                    winNumbers = winNumbers + 1;
                }
                if (DateUtils.isSameDay(new Date(), entity.getCreateDate())) {
                    todayAmountList.add(entity.getAmount().intValue());
                    todayTotalAmount = todayTotalAmount.add(entity.getAmount());
                    todayTransactionsNumbers = todayTransactionsNumbers + 1;
                    todayTotalProfit = todayTotalProfit.add(entity.getProfitAmount().subtract(entity.getAmount()));
                    if (entity.getProfitStatus() == ProfitStatusEnum.PROFIT.getCode()) {
                        todayWinNumbers = todayWinNumbers + 1;
                    }
                } else {
                    historyAmountList.add(entity.getAmount().intValue());
                    historyTransactionsNumbers = historyTransactionsNumbers + 1;
                    historyTotalProfit = historyTotalProfit.add(entity.getProfitAmount().subtract(entity.getAmount()));
                    if (entity.getProfitStatus() == ProfitStatusEnum.PROFIT.getCode()) {
                        historyWinNumbers = historyWinNumbers + 1;
                    }
                }
            }
            if (transactionsNumbers != 0) {
                productWinRate = DecimalUtils.div(winNumbers, transactionsNumbers);
            }
            if (todayTransactionsNumbers != 0) {
                todayProductWinRate = DecimalUtils.div(todayWinNumbers, todayTransactionsNumbers);
            }
            if (historyTransactionsNumbers != 0) {
                historyProductWinRate = DecimalUtils.div(historyWinNumbers, historyTransactionsNumbers);
                isNewUser = false;
            }
            if (todayTotalAmount.compareTo(BigDecimal.ZERO) != 0) {
                todayProductProfitRate = todayTotalProfit.divide(todayTotalAmount, 6, BigDecimal.ROUND_HALF_UP);
            }

            //交易笔数小于10笔 不重新计算收益率
            if (transactionsNumbers <= 8) {
                //productOdds = Double.valueOf(ProductEnum.getEnumByProductCode(productCode).getOdds());
                LOGGER.info("============ 交易笔数小于10笔 不重新计算收益率, totalProfit:{}, userCode:{}, productCode:{} ==============", totalProfit, userCode, productCode);
            } else {
                //收益为负的情况不改变收益率
                if (totalProfit.compareTo(BigDecimal.ZERO) <= 0) {
                    //productOdds = Double.valueOf(ProductEnum.getEnumByProductCode(productCode).getOdds());
                    LOGGER.info("============ 收益为负的情况不改变收益率, totalProfit:{}, userCode:{}, productCode:{} ==============", totalProfit, userCode, productCode);
                } else {
                    double averageTodayAmount = 0;
                    double averageHistoryAmount = 0;
                    if (todayTransactionsNumbers != 0) {
                        averageTodayAmount = CalculatorUtils.getAverage(todayTransactionsNumbers, todayAmountList.stream().toArray(Integer[]::new));
                    }
                    if (historyTransactionsNumbers != 0) {
                        averageHistoryAmount = CalculatorUtils.getAverage(historyTransactionsNumbers, historyAmountList.stream().toArray(Integer[]::new));
                    }
                    //离散系数
                    double coefficient = CalculatorUtils.getDispersionCoefficient(transactionsNumbers, amountList.stream().toArray(Integer[]::new));
                    //double historyCoefficient = CalculatorUtils.getDispersionCoefficient(historyTransactionsNumbers, historyAmountList.stream().toArray(Integer[]::new));
                    UserDepositVO userDeposit = depositService.getUserDeposit(userCode);
                    LOGGER.info("================= 离散系数 coefficient:{},userCode:{},averageTodayAmount:{},averageHistoryAmount:{} ==================", coefficient, userCode, averageTodayAmount, averageHistoryAmount);
                    String productCalculateOdds = this.calculateProductOddsByProfit(isNewUser, userCode, todayProductProfitRate, totalProfit, productCode, DecimalUtils.roundToString(productProfit), productOdds, todayProductWinRate, historyProductWinRate, historyTotalProfit, todayTotalProfit, coefficient, averageTodayAmount, averageHistoryAmount, userDeposit.getTotalDepositAmount());
                    if (new BigDecimal(productOdds).compareTo(new BigDecimal(productCalculateOdds)) > 0) {
                        productOdds = productCalculateOdds;
                    } else {
                        LOGGER.info("============== 当前赔率低于重新计算的赔率，固不改变原先赔率，userCode:{}, currentProductOdds:{},productCalculateOdds:{} =============", userCode, productOdds, productCalculateOdds);
                    }
                }
            }
        }
        odds.setProductCode(productCode);
        odds.setProductWinRate(DecimalUtils.roundToString(productWinRate));
        odds.setTotalProfit(totalProfit.intValue());
        odds.setTransactionsNumbers(transactionsNumbers);
        odds.setProductOdds(productOdds);
        odds.setMarketOdds(productOdds);
        if (new BigDecimal(productOdds).compareTo(new BigDecimal(product.getMiniOdds())) < 0) {
            productOdds = new BigDecimal(product.getMiniOdds()).stripTrailingZeros().toPlainString();
            LOGGER.info("========== 用户赔率不能低于最低值 productOdds:{}, userCode:{}, MiniOdds:{} ==========", productOdds, userCode, product.getMiniOdds());
        }
        if (new BigDecimal(productOdds).compareTo(new BigDecimal(product.getOdds())) > 0) {
            productOdds = new BigDecimal(product.getOdds()).stripTrailingZeros().toPlainString();
            LOGGER.info("========== 大盘不能高于最高值 productOdds:{}, userCode:{}, Odds:{} ==========", productOdds, userCode, product.getOdds());
        }

        try {
            UserProductOdds userProductOdds = new UserProductOdds();
            userProductOdds.setOdds(productOdds);
            userProductOdds.setProductCode(productCode);
            userProductOdds.setUserCode(userCode);
            userProductOdds.setTriggerType(TriggerTypeEnum.USER_TRIGGER.getCode());
            userProductOdds.setMarketOdds(productProfit.toPlainString());
            userProductOdds.setProductOdds(product.getOdds());
            userProductOdds.setProductMiniOdds(product.getMiniOdds());
            userProductService.insertUserProductService(userProductOdds);
        } catch (Exception e) {
            LOGGER.error("============== 更新赔率失败e:{} ============", e);
        }
        LOGGER.info("================ 结算之后重新计算收益率 odds:{} ================", JSONObject.toJSONString(odds));
        return odds;
    }

    /**
     * 计算收益率
     *
     * @param isNewUser
     * @param userCode
     * @param todayProductProfitRate
     * @param totalProfit
     * @param productCode
     * @param productOdds
     * @param currentProductOdds
     * @param todayProductWinRate
     * @param historyProductWinRate
     * @param historyTotalProfit
     * @param todayTotalProfit
     * @param coefficient
     * @param averageTodayAmount
     * @param averageHistoryAmount
     * @param totalDepositAmount
     * @return
     */
    private String calculateProductOddsByProfit(boolean isNewUser, String userCode, BigDecimal todayProductProfitRate, BigDecimal totalProfit, String productCode, String productOdds, String currentProductOdds, BigDecimal todayProductWinRate, BigDecimal historyProductWinRate, BigDecimal historyTotalProfit, BigDecimal todayTotalProfit, double coefficient, double averageTodayAmount, double averageHistoryAmount, BigDecimal totalDepositAmount) {

        String odds = DecimalUtils.roundToString(new BigDecimal(productOdds).subtract(new BigDecimal(0.02)));
        String odds14 = DecimalUtils.roundToString(new BigDecimal(productOdds).subtract(new BigDecimal(0.14)));
        if (todayProductProfitRate.compareTo(new BigDecimal(0.1)) > 0) {
            LOGGER.info("========== 用户收益率超过10% 大盘降14% userCode:{}, productCode:{}, todayProductProfitRate:{} =============", userCode, productCode, todayProductProfitRate);
            return odds14;
        }

        if (isNewUser && totalProfit.compareTo(totalDepositAmount.multiply(new BigDecimal(2))) >= 0) {
            LOGGER.info("============ 新客 低12% 净利润≥充值金额的两倍 -12% userCode:{}, totalProfit:{}, productCode:{}, 原先收益率:{},odds:{},averageTodayAmount:{},totalDepositAmount:{} =============", userCode, totalProfit, productCode, productOdds, odds, averageTodayAmount, totalDepositAmount);
            return odds14;
        }

        //新客 低2%
        if (isNewUser && totalProfit.compareTo(new BigDecimal(averageTodayAmount * 2)) > 0) {
            LOGGER.info("============ 新客 低2% 净利润≥单笔交易限额 -2% userCode:{}, totalProfit:{}, productCode:{}, 原先收益率:{},odds:{},averageTodayAmount:{} =============", userCode, totalProfit, productCode, productOdds, odds, averageTodayAmount);
            return DecimalUtils.roundToString(new BigDecimal(productOdds).subtract(new BigDecimal(0.03)));
        }

        //前一天的净利润大于0，小于单笔交易限额
        //前一天的胜率≥55%
        if (historyProductWinRate.compareTo(new BigDecimal(0.55)) >= 0 || (historyTotalProfit.compareTo(BigDecimal.ZERO) > 0 && historyTotalProfit.compareTo(new BigDecimal(averageHistoryAmount * 2)) <= 0)) {
            LOGGER.info("============ 老客 低2% 前一天的净利润大于0 | 小于单笔交易限额 | 前一天胜率≥55% userCode:{}, totalProfit:{}, productCode:{}, historyTotalProfit:{},historyProductWinRate:{},原先收益率:{},averageHistoryAmount:{} =============", userCode, totalProfit, productCode, historyTotalProfit, historyProductWinRate, productOdds, averageHistoryAmount);
            return odds;
        }

        //老客 低5%
        //连续2天每天的胜率都大于55%
        //累计日均净利润大于单笔交易限额
        //前1天交易金额离散系数大于0.5
        if ((new BigDecimal(0.55).compareTo(todayProductWinRate) < 0 && new BigDecimal(0.55).compareTo(historyProductWinRate) < 0) || Double.compare(coefficient, 0.5D) > 0 || (todayTotalProfit.compareTo(new BigDecimal(averageTodayAmount * 2)) > 0 && historyTotalProfit.compareTo(new BigDecimal(averageHistoryAmount * 2)) > 0)) {
            LOGGER.info("============ 老客 低5% 根据收益调整收益率 -5% userCode:{}, totalProfit:{}, productCode:{}, todayProductWinRate:{}, historyProductWinRate:{},historyTotalProfit:{},原先收益率:{},averageTodayAmount:{},averageHistoryAmount:{} =============", userCode, totalProfit, productCode, todayProductWinRate, historyProductWinRate, historyTotalProfit, productOdds, averageTodayAmount, averageHistoryAmount);
            return DecimalUtils.roundToString(new BigDecimal(productOdds).subtract(new BigDecimal(0.05)));
        }

        return currentProductOdds;
    }

}
